package compiler.ast;

import java.io.PrintStream;
import java.util.ArrayList;

import compiler.parser.SemanticException;
import compiler.symboltable.SymbolTable;
import compiler.tac.BasicBlock;
import compiler.tac.EntryBlock;
import compiler.tac.ExitBlock;
import compiler.tac.ThreeAddressCode;


public class FuncDeclaration {
	Identifier name;
	ArrayList<Identifier> paramList;
	SymbolTable localVars;
	ArrayList<Statement> body;
	Expression returnvalue;
	boolean isFunc;
	
	public FuncDeclaration(Identifier name) {
		this.name = name;
		paramList = new ArrayList<Identifier>();
		body = new ArrayList<Statement>();
		returnvalue = null;
		isFunc = false;
	}

	public void checkReturn(int line, int pos) throws SemanticException
	{
		boolean found = false;
		for(Statement stat: body)
			if(stat.getClass().getSimpleName().equals(ReturnStatement.class.getSimpleName()))
			{
				found = true;
				if(!isFunc && stat.getreturnValue()!=null)
					throw new SemanticException(line, pos, "Function type with return value in front of this position!");
				else if(isFunc && stat.getreturnValue() == null)
					throw new SemanticException(line, pos, "Procedure type without return value in front of this position!");
			}
		if(!found && isFunc)
			throw new SemanticException(line, pos, "Function/Procedure need return statement in front of this position!");
		else if(!found && !isFunc)
			body.add(new ReturnStatement(null));
	}
	
	
	public void printDotFormat(PrintStream out, String node) {
		String type = (isFunc)? "function" : "procedure";
		
		if (paramList.size() > 0) {
			out.println(node+" [label=\"{"+type+"|{"+name.name()+params()+"}}\"];");
		} else {
			out.println(node+" [label=\"{"+type+"|"+name.name()+"}\"];");
		}
		
		String bodyNode = node+"0";
		out.println(node+"->"+bodyNode+";");
		out.println(bodyNode+" [label=\"body\"];");
		
		int order = 0;
		for (Statement stmt : body) {
			out.println(bodyNode+"->"+bodyNode+order+";");
			stmt.printDotFormat(out, bodyNode+order);
			order++;
		}
	}
	
	private String params() {
		String ret = "";
		for (Identifier param : paramList) {
			ret += "|" + param.name();
		}
		return ret;
	}
	
	public void setIsFunc(boolean isfunc)
	{
		this.isFunc = isfunc;
	}
	public boolean getIsFunc()
	{
		return this.isFunc;
	}
	
	public void setLocalVars(SymbolTable t)
	{
		localVars = t;
	}
	
	public SymbolTable getLocalVars(SymbolTable t)
	{
		return localVars;
	}
	
	public void setParamList(ArrayList<Identifier> params)
	{
		paramList = params;
	}
	public ArrayList<Identifier> getParamList()
	{
		return paramList;
	}
	
	public void setBody(ArrayList<Statement> b)
	{
		body = b;
	}
	public ArrayList<Statement> getBody()
	{
		return body;
	}
	public ThreeAddressCode toThreeAddressCode(ThreeAddressCode parent)
	{
		ThreeAddressCode code = new ThreeAddressCode();
		code.setST(this.localVars);
		code.addParent(parent);
		parent.addChildren(code);
		
		EntryBlock entryb = new EntryBlock(name.name());
		code.setEntry(entryb);
		
		ExitBlock exitb = new ExitBlock();
		code.setExit(exitb);
		
		// TODO: Do we need to add an edge from entry block to exit block?
		
		BasicBlock basicb = new BasicBlock();
		BasicBlock.add(entryb, basicb);
		code.addBlock(basicb);
		
		for (Statement stmt : body) {
			basicb = stmt.toThreeAddressCode(code, basicb, exitb);
		}
		
		// when the module body doesn't end with a return statement
		if (basicb != null) {
			(new ReturnStatement(null)).toThreeAddressCode(code, basicb, exitb);
		}
		
		// check existence of return value
		for(BasicBlock block : exitb.getPreds()) {
			boolean rvalue = block.hasReturnValue();
			if (isFunc && !rvalue) {
				System.out.println("Warnning: function '"+name.name()+"' has an path missing return value.");
			} else if (!isFunc && rvalue) {
				System.out.println("Warnning: procedure '"+name.name()+"' has an path returning some value.");
			}
		}
		
		return code;
	}
}
